<template>
    <div v-show="visible" ref="contextMenuRef"
         :style="{'--context-menu-top': `${pos.y}px`, '--context-menu-left': `${pos.x}px`,'--context-menu-width': `${width}px`}"
         class="right-click-menu"
         @click.stop>
        <ul>
            <template v-for="(item, index) in menus" :key="index">
                <li v-if="!item.hidden"
                    :class="{'contextmenu-item': true, 'disabled': item.disabled, 'separator': item.separator, 'has-children': item.children, 'active': active === index}"
                    @click.stop.prevent="onMenuItemClick(item)"
                    @mouseenter="onMouseEnter(item, index, $event)">
                    <span class="icon">
                        <svg aria-hidden="true" v-if="item.icon">
                            <use :xlink:href="`#${item.icon}`"/>
                        </svg>
                    </span>
                    <span class="label">{{ item.label }}</span>
                    <span v-if="item.shortcut" class="shortcut">
                        {{ item.shortcut }}
                    </span>
                    <span v-if="item.children" class="arrow">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" >
                            <path fill="currentColor" d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z"/>
                        </svg>
                    </span>
                </li>
                <sub-menu v-if="item.children && active === index" v-model="item.children"
                          :data="submenuData"
                          :width="width"
                          :visible="true"
                          :before-open="beforeOpen"
                          @click="onMenuItemClick"/>
            </template>
        </ul>
    </div>
</template>

<script setup lang="ts">
import {onClickOutside} from '@vueuse/core';
import {inject, nextTick, onMounted, provide, reactive, ref, watch} from 'vue';
import SubMenu from './index.vue';

interface IMenuItemProps {
    label?: string
    icon?: string
    hidden?: boolean
    disabled?: boolean
    separator?: boolean
    children?: IMenuItemProps[]
    shortcut?: string
    onClick?: (data: Array<any>, menu: IMenuItemProps) => boolean
}

interface IContextMenuProps {
    data:ContextmenuDataProps
    visible?: boolean
    width?: number
    beforeOpen?: (parameter: any, menus: Array<IMenuItemProps>) => boolean
}

type PasteType = 'copy'|'move'
export interface ContextmenuProps {
    data: ContextmenuDataProps
    paste?: PasteProps
    menus: Array<any>
    onBeforeOpen: (parameter: any, menus: Array<any>) => boolean
    onOpen: (event: MouseEvent, row?: any) => void
    onClick?: (menu: any, data: any[]) => void
}

export interface ContextmenuDataProps {
    position: number[],
    parameter: any
}

export interface PasteProps {
    type: PasteType
    path: string
    data: Array<any>
}

const props = withDefaults(defineProps<IContextMenuProps>(), {
    visible: false,
    width: 290,
    beforeOpen: () => true,
})

const menus = defineModel<Array<IMenuItemProps>>({default: []});

const emit = defineEmits(['click']);

const visible = ref(props.visible);
const contextMenuRef = ref();

// 计算菜单高度
const calculateSubmenuHeight = (menus: Array<IMenuItemProps>) => {
    const spCount = menus.filter(item => item.separator).length;
    const muCount = menus.filter(item => item.label && !item.hidden).length;
    // 8 * 2: padding 8; 18: svg:height 18px
    return spCount * 9 + muCount * (8 * 2 + 18) + 20;
};

const height = calculateSubmenuHeight(menus.value); // 菜单高度（假设每个菜单项高度为 30px;头尾 padding 10则共计20）

// 弹出菜单
const pos = reactive({x: props.data.position[0], y: props.data.position[1]});
watch(() => props.data.position, (p) => {
    active.value = null;
    const status = props.beforeOpen(props.data.parameter, menus.value);
    pos.x = p[0] + props.width > window.innerWidth? window.innerWidth - props.width:p[0];
    pos.y = p[1] + height > window.innerHeight? window.innerHeight - height:p[1];
    nextTick(() => visible.value = status)
})

// 弹出子菜单
const active = ref<number | null>(null);
const submenuData = reactive<ContextmenuDataProps>({
    position: [0, 0],
    parameter: props.data.parameter
});
const onMouseEnter = (item: IMenuItemProps, index: number, event: any) => {
    if (item.disabled) return;

    active.value = item.children ? index : null;

    if (item.children && contextMenuRef.value) {
        if (props.beforeOpen(submenuData.parameter, item.children)) {
            const rect = event.target.getBoundingClientRect();

            // 计算子菜单位置
            submenuData.position[0] = rect.right + 1;
            submenuData.position[1] = rect.top;

            // 检查右侧空间
            if (submenuData.position[0] + props.width > window.innerWidth) {
                submenuData.position[0] = rect.left - props.width - 1;
            }

            // 检查底部空间
            const submenuHeight = calculateSubmenuHeight(item.children);
            if (submenuData.position[1] + submenuHeight > window.innerHeight) {
                submenuData.position[1] = window.innerHeight - submenuHeight;
            }
        } else {
            active.value = null;
        }
    }
};


const hide = () => {
    active.value = null;
    visible.value = false;
};

const onMenuItemClick = (item: IMenuItemProps) => {
    if (!item) {
        emit('click')
        hide();
        return;
    }

    if (item.disabled || item.children) return;

    if (typeof item.onClick === "function") {
        item.onClick(props.data.parameter, item);
        emit('click')
        hide();
    } else {
        emit('click', item, props.data.parameter);
        hide();
    }

};
// 点击菜单之外的任一区域都关闭菜单
onClickOutside(contextMenuRef, () => hide(), {ignore: []})
// 暴露方法给父组件
defineExpose({
    hide,
});
</script>

<style lang="scss" scoped>
.right-click-menu {
    font-family: serif;
    font-size: 12px;
    color: #333;
    position: fixed;
    background-color: #fff;
    border: 1px solid rgba(0, 0, 0, 0.1);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
    border-radius: 10px;
    z-index: 1000;
    top: var(--context-menu-top);
    left: var(--context-menu-left);
    width: var(--context-menu-width);
    padding: 10px 0;

    ul {
        list-style: none;
        margin: 0;
        padding: 0;
    }

    .contextmenu-item {
        display: flex;
        align-items: center;
        padding: 8px 16px;
        cursor: default;
        position: relative;
    }

    .contextmenu-item.active,
    .contextmenu-item:hover {
        background-color: #f0f0f0;
    }

    .contextmenu-item.disabled {
        color: #999;
        cursor: not-allowed;
    }

    .contextmenu-item.separator {
        height: 1px;
        background-color: #eee;
        margin: 4px 0;
        padding: 0;
        cursor: default;
    }

    .icon {
        margin-right: 8px;
    }

    .icon,
    svg {
        display: flex;
        height: 18px;
        width: 18px;
    }

    .label {
        flex: 1;
        display: flex;
    }

    .shortcut {
        margin-left: 16px;
        color: #999;
    }

    .arrow {
        margin-left: 8px;
        font-size: 12px;
        color: #999;
    }
}

.right-click-menu .has-children {
    padding-right: 24px;
}
</style>
